chore(deps): resolve @objectstack/spec at 17.3.0 in the lockfile - #7685
chore(deps): resolve @objectstack/spec at 17.3.0 in the lockfile#7685claude[bot] wants to merge 23 commits into
Conversation
Moves the `@objectstack/spec` resolution in `pnpm-lock.yaml` from 17.2.0 to
17.3.0. Every one of the 30 manifests that declare the dependency already
carried a range admitting it (`^17.0.0` / `^17.1.0` / `^17.2.0`), so the
declared specifiers are untouched and no published dependency declaration
changes by a byte; `check-changeset-presence` confirms the range independently
("0 of them a manifest whose published contract moved").
The bump is committed on its own because the reconciliation it forces is not
mechanical: 17.3.0 reddens 14 test files / 36 assertions across six packages,
and at least four of those reds are public-contract decisions rather than pin
updates. The PR body carries the full measured red set.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0114Ytxr5sM1vdW19Y9WAx6E
CI red — expected, measured in advance, and ⛔ not fixable inside this branchPM of record for this dispatch ( What is failing
Three findings, verbatim from the job log:
⭐ This is the PR's own prediction firing, not a surpriseThe PR body already recorded this gate at exit 1 with these three findings, before CI ran. CI reproduces them exactly. The red is the deliverable here — this branch exists to make the 17.3.0 reconciliation measurable, and it says so in its first line: ⛔ not a merge candidate. All three findings are the same upstream fact the PR documents: 17.3.0 added Why no fix is being pushed⛔ Not because it is hard, and ⛔ not "flake" — this is real and deterministic. Three reasons, in order:
⛔ No re-run. A re-run is for confirming a failure that is not this PR's, or one that died before any test body ran. This one is this PR's, it is reproducible, and its cause is named. What actually unblocks it#7122 moves to the decision box. The card was dispatched as a mechanical pin bump; the measurement shows the bump forces a set of public-contract rulings. Per the standing rule, that means the dev stops and the card returns for adjudication — ⛔ never a silent re-decision by this seat. This branch stays draft and parked as the evidence, and keeps This comment is the standing-down record for this failure; ⛔ a further red on this same head needs no second one. Generated by Claude Code |
ℹ️ Console Performance Budget — not measuredThis run did not produce a console bundle to measure, so there is no pass/fail verdict for the performance budget. This is not a budget violation. Nothing was measured — the numbers a real violation would carry are simply absent.
See the workflow run for details. No package size report: it is only generated from a complete package build, so a partial one is never shown. |
⛔ 撤回上一条止血评论的范围 —— 它把可机械修复的部分也一并停手了,这是错的
维护者指令(逐字):「7685 红了,你已应该修复」。本条依此执行,并如实记录我判断错在哪。 我错在哪上一条止血评论(5548718074)说本 PR 的红「⛔ 不可在本分支内修复」,理由分三条。第 2 条站不住:我在同一条评论里写明「findings 2–3 是机械的」(两个陈旧 ALLOW 条目,门禁原话就是 "Delete the entry"),然后又以**「修了也不能让本 PR 变绿」**为由不修。 ⇒ 「修好一个真实的、机械的、在范围内的缺陷,不足以让整个 PR 变绿」不是不修它的理由。我把决策闸门套在了整个红面上,而实际上只有其中一部分真正需要裁决。红面里可机械修复的那部分,是我该修而没修的。 现在执行已派发 dev 驱动本 PR 向绿,修一切「修法已由门禁自身或 pin 自己的 docblock 写明」的项:
⛔ 仍然不修、确实需要您裁决的
⇒ #7122 保留的将只剩真正的分叉,而不是像现在这样把整片红面都挂在裁决后面。这正是我上一条该做而没做的收窄。 不变的部分
Generated by Claude Code |
ℹ️ Console Performance Budget — not measuredThis run did not produce a console bundle to measure, so there is no pass/fail verdict for the performance budget. This is not a budget violation. Nothing was measured — the numbers a real violation would carry are simply absent.
See the workflow run for details. No package size report: it is only generated from a complete package build, so a partial one is never shown. |
…bjectstack/spec 17.3.0 (objectui#7122) Three repairs, each prescribed by the thing that failed. 1. `SchemaDiffEntryKind` gained `unreachable` in 17.3.0, so app-shell's deliberately-total `DIFF_LABEL` map stopped compiling — TS2741, the single error behind the `Build packages: failure` CI reported on this branch. The map is total on purpose so an upstream addition fails the build instead of rendering a blank cell; that mechanism worked. Labelled "Not checked — remote unreachable", following the spec's own ruling that this kind asserts NOTHING about the remote schema and must never be surfaced as "schema changed". 2. 17.3.0 began exporting `FlowNodeLike` from `@objectstack/spec/system`, so app-shell's two same-named local declarations became shadows. Measured rather than assumed: the spec's `id?` is OPTIONAL where the inspector's is REQUIRED, and assignability runs one way only, so importing the spec export would silently widen away the guarantee `locateFlowNode`'s callers rest on. Genuine dialects ⇒ renamed to `InspectorFlowNode` and `ScopeFlowNode`, with the tripwire rows the guard asks for in `page-nav-misc-spec-parity.test.ts`. 3. The `BreakpointName` / `BreakpointColumnMap` ALLOW entries were written self-expiring: they covered the interval while objectstack#11027's retirement was merged but unreleased, and their own text said to delete them on the pin bump and move the names to the absence pin. 17.3.0 published the retirement, ratchet 3 failed them as excusing nothing, so both are deleted and both names are pinned absent instead. `node scripts/check-spec-symbol-derivation.mjs` — exit 0 (was 1 with three findings): 1345 files scanned, 18 declared dialects. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0114Ytxr5sM1vdW19Y9WAx6E
❌ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. Which half objected:
📦 Bundle Size Report
Size Limits
|
…the prescription (objectui#7122) `@objectstack/spec` 17.3.0 stripped the `#NNNN` issue-number citations from its refusal messages while keeping the prescriptive half intact. Sixteen assertions were pinned on the stripped half and went red; each is re-pointed at what the refusal still carries, which is the half the pins' own comments name as the point — "the prescription is the half that makes the refusal actionable for an author; asserting only `success === false` would stay green if it were reduced to 'Invalid input'". - `submitRedirect.test.ts` (13): `#7496` -> `ruled 2026-08-11`. The refusal still cites its governing ruling, now by date rather than by issue number; the key-name and non-genericity assertions are untouched. - `export-options-spec-parity.test.ts`: `/8010|1301/` -> the surviving format list and the `os migrate meta` command, i.e. the actionable repair itself. - `report-chart-query-spec-parity.test.ts`: `'#4876, ADR-0049 D2'` -> the ADR reference, which survived, plus the `Delete the key.` prescription. Separately, the `PreviewModeConfig` doc-provenance ratchet FIRED and is retired exactly as its own docblock prescribed ("correct the README line … and then delete this guard, which has no reason to outlive the sentence it protects").⚠️ The docblock expected that at spec major 18 — objectstack#11846 registered the retirement in `RETIRED_DEFS_BY_MAJOR[18]` — and it arrived in a MINOR instead, one of four public type exports 17.3.0 removed with no major signal. The capability is untouched: `previewMode` is host-supplied and stays. Verified: `pnpm exec vitest run` over the five affected files — 5 passed, 150 tests passed, exit 0. The edited gate script's own pin suites (`check-spec-symbol-derivation.test.ts`, `check-doc-component-types.test.ts`) — 2 passed, 108 tests, exit 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0114Ytxr5sM1vdW19Y9WAx6E
…0 bump forced (objectui#7122) The branch previously declared 'no release' with empty frontmatter, which was true while its only diff was the lockfile. It no longer is: `@object-ui/app-shell` needed a source fix to compile against 17.3.0 at all, and `@object-ui/auth`'s README carried a provenance claim 17.3.0 falsified. Both are declared as patches; the lockfile-only changeset keeps its own empty-frontmatter declaration. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0114Ytxr5sM1vdW19Y9WAx6E
…ation` / `patch` (objectui#7122) `SPEC_ACTION_KEYS` declares itself as "every property `@objectstack/spec`'s `ActionSchema` declares", and `actionKeys.pin.test.ts` walks the schema and fails the day that drifts. 17.3.0 added `operation` and `patch` — the declarative single-record field write mirroring a list view's `bulkActionDefs` — so the inventory was stating something false. Restating the spec, NOT adopting a feature, exactly as the `description` entry above it records for the same situation: `ActionDef` still does not declare either key, no runner branch reads them, and no action surface forwards them. That omission is deliberate under the maintainer's 2026-09-05 ruling on this bump — record them as justified omissions now, forward only a key a runtime actually reads once its semantics are read from upstream, no speculative forwarding.⚠️ Deliberately checked rather than assumed: the inventory feeds `KNOWN_ACTION_KEYS`, which `classifyActionKeys` consults — and that function only builds `unknown` / `retired` lists for a dev-mode console warning. It rejects nothing and drops nothing, so widening it does not move what any published `@object-ui/*` package accepts or refuses. Clause-② stays `no`. Verified: `pnpm exec vitest run packages/core/src/actions/` — 24 files, 427 tests, exit 0. `node scripts/check-action-forward-parity.mjs` — exit 0, still 19 justified omissions / 7 known gaps, so no new gap was opened. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0114Ytxr5sM1vdW19Y9WAx6E
❌ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. Which half objected:
📦 Bundle Size Report
Size Limits
|
…al (objectui#7122) `scripts/__tests__/doc-version-claims.test.ts` is a ratchet over version literals on doc surfaces, and my first correction of this sentence introduced one. Its prescription is explicit — prefer DELETING the literal and pointing at the truth instead, because that is the only spelling that cannot go stale (objectui#3645 froze a spec range across 36 READMEs for thirteen majors exactly this way). The sentence now names the upstream retirement and the fact that the resolved spec no longer exports the symbol, with no version to go stale. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0114Ytxr5sM1vdW19Y9WAx6E
❌ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. Which half objected:
📦 Bundle Size Report
Size Limits
|
…ls on their ruling not its issue number Two families of red from the `@objectstack/spec` 17.3.0 bump, both of which executed an existing ruling rather than making a new one. `manage_org_presentation` (3 assertions, `packages/fields`). 17.3.0 declares a ninth platform capability; `CURATED_CAPABILITY_LABELS` carried eight, so the spec-parity pin failed in the "spec added a member" direction it was built for (objectui#6285 already ruled the curated set IS the spec's set, both ways). The label is the spec artifact's own (`Manage Organization Presentation`), not an invented one. Authored in all ten locale packs and in `useFieldTranslation.ts`, which the widget's own docblock requires of any edit to that list; the nine non-English strings are composed from each pack's established sibling vocabulary (`manage_org_users`, `manage_platform_settings`) rather than machine-translated, and a native review pass is welcome on them. The `#7496` token (14 assertions, 3 files). Every one was `expected '<message>' to contain '#7496'`, while the sibling assertions on `RELATIVE path only` stayed green: 17.3.0 kept all five refusals and their reasoning and only restated provenance as `(ruled 2026-08-11)`. The pins asserted the citation FORM, not the citation — the objectui#7702 defect shape. Re-pinned on the durable property via a documented `CITES_ITS_RULING`, which still discriminates the spec's prose from a local hand-written sentence (the load-bearing mutation probe in `ObjectForm.submitRedirect.test.tsx`'s header). Not re-pinned to the new prose verbatim, which would only move the brittleness. Part of #7122 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0114Ytxr5sM1vdW19Y9WAx6E
❌ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. Which half objected:
📦 Bundle Size Report
Size Limits
|
…re-derive two negative pins The rest of the 17.3.0 red set outside the `#7496` and capability families. Every verdict below is read off the installed artifact, not off an assertion name or a changelog. `registry-inputs-spec-parity` (5 assertions). Four of the five were ONE fact wearing four assertion names, two of which appear to point in opposite directions ("a published key must lose its entry" vs "spec no longer declares object-grid.defaultSort"). Neither reading is right: 17.3.0 made `object-grid.defaultSort` an ADR-0087 D2 TOMBSTONE — member is `z.never().optional()`, description opens `[REMOVED]`, `safeParse` answers `invalid_type` `expected: 'never'` at that path. It therefore left the authorable set while staying listed, which is precisely how the eight tombstones before it were harvested. Its exemption, its GA-pending entry and its carve-out row are deleted, and the block's prose parenthetical claiming a tombstone "would NOT make them stale here" is corrected — its own cited counter-example, the record_picker trio, was harvested exactly that way. The fifth emptied `OFF_SPEC_ARM_EXEMPTIONS` entirely: both arms converged upstream in the direction their own reasons named. `element:number.filter` now accepts `[]` and judges content at `filter.0` (objectui#6206's first option); `object-grid.data` now answers `invalid_union` at `data.provider`, the discriminated `ViewDataSchema` shape `ObjectGridSchema.data` already resolved to (objectui#6207's "convergence is upstream"). Neither declaration was edited — the contract moved to them. #6206 and #6207 are closable as resolved-upstream. Two negative pins whose premise evaporated, both re-derived rather than inverted: - `masterDetailFormTypeManifest` pinned that the manifest enum does NOT make a value unpublishable. 17.3.0 closed that gap deliberately: `invalid_value` naming the honoured pair, plus a bespoke prescription for the near-miss `wizard` citing ADR-0001 and the renderer measurement. The pin now records that both authorities agree, checks each independently, keeps a positive control on `simple`/`tabbed`, and pins the refusal's SHAPE so "the enum closed" cannot be confused with a dropped key or a cross-field rule. - `LocationField.optionalKeys` pinned that the location value schema is non-strict. 17.3.0 closed the shape (`unrecognized_keys` naming the retired `latitude`/`longitude` pair). The test carried a note for this exact event — "the guard could then be delegated to it" — and the re-derived answer is NO: the fence guards the object handed to `onChange` and no parse stands on that path, so strictness changed the CONSEQUENCE of a spread regression, not the widget's exposure. The key-level assertions stay; the strictness is pinned. Part of #7122 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0114Ytxr5sM1vdW19Y9WAx6E
❌ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. Which half objected:
📦 Bundle Size Report
Size Limits
|
❌ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. Which half objected:
📦 Bundle Size Report
Size Limits
|
…pointing Two changesets for the work in dc82492 and e328e36: a released one for the `manage_org_presentation` label (`@object-ui/fields`, `@object-ui/i18n`), and an empty-frontmatter one for the test-only pin re-pointing and exemption harvest, which releases no package. Part of #7122 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0114Ytxr5sM1vdW19Y9WAx6E
❌ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. Which half objected:
📦 Bundle Size Report
Size Limits
|
…thorisation, to 17.3.0's measured cost
Raising a gate ceiling is a gate weakening and sits on the manual floor, so it
is a human's decision, not an agent's. The authorisation, verbatim:
维护者 sam@objectstack.ai 于本轮明确授权:「抬上限,把 7685 弄绿」
recorded on objectui#7122 as decision batch 1 item 1 = "B + A" — a one-time,
cause-recorded adjustment of exactly the measured delta, with the upstream card
filed alongside.
⛔ NOT routine growth, and the body must not read as if it were. The residue is
`@objectstack/spec` 17.3.0's own browser-dist growth: +292.2 KB gzip on the spec
package, whose measured mechanism is that 17.3.0 lengthened the Zod
`.describe()` doc strings shipped in the browser build — authoring
documentation prose, delivered on every page load.
⛔ NOT duplication either. That was the larger, separate problem and this branch
already fixed it by moving the `@objectstack/*` family together in the lockfile:
−671 KB, with markers unique to 17.2.0 falling from 92.3% to 1.0% of 104 and the
single survivor accounted for. No chunk entered or left the closure.
⭐ The honest long-term fix is upstream and this ceiling is its marker, not its
answer: a `describe()`-stripped browser build returns ~292 KB to EVERY consumer
of the spec. Filed as objectstack#16063 and recorded in the gate as the restore
condition.
Four constants, two ceiling/baseline pairs, measured by `pnpm build` (exit 0,
43/43 — 43/43 CACHED on the re-read, which observes rather than argues the
"nothing here reaches the bundler" invariant) reading
apps/console/dist/eager-closure.json on 34a1578:
MAX_EAGER_CLOSURE_GZIP_BYTES 3_268_000 -> 3_597_000
BASELINE 3_222_314 -> 3_551_191 (48/517 -> 50/518)
PER_CHUNK_GZIP_CEILINGS.vendor-objectstack 967_000 -> 1_254_000
PER_CHUNK_BASELINE.vendor-objectstack 948_461 -> 1_235_029
Headroom is 45,809 (0.50x) and 18,971 (0.21x) of the 91,136-byte regression the
gate exists to catch — the proportions the retiring pairs carried (0.50x, 0.20x)
— so the gate still reds on a repeat of objectui#5266. `check:eager-closure`
exits 0 with sensitivity ✅ on all five ceilings. The three per-chunk lines that
still pass (i18n-locales, framework, ui-components) were NOT touched.
The unit test moves with the constants because it deliberately retypes
`BASELINE.gzipBytes` rendered (3146.8 -> 3468.0) rather than deriving it, and
the baseline prose pins are re-pointed at the new commit.
Part of #7122
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0114Ytxr5sM1vdW19Y9WAx6E
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
PM review — green at
|
| pair | ceiling | baseline | headroom | ×REGRESSION_THIS_GATE_MUST_CATCH_BYTES |
|---|---|---|---|---|
| aggregate | 3,597,000 | 3,551,191 | 45,809 | 0.503× |
vendor-objectstack |
1,254,000 | 1,235,029 | 18,971 | 0.208× |
Both land on the file's own retiring precedent (0.501× and ~0.20×), not on a number chosen to fit. The ceiling covers the measured 3467.8 KB = 3,551,027 bytes with the drift margin CI needs, since CI weighs the merge ref and no local tree can equal it.
⭐ The sensitivity guard was not touched. This was the specific thing I withheld sign-off for. The diff to scripts/__tests__/check-eager-closure-budget.test.ts is 7 lines; it('is exactly one regression wide, from either side of the line') is unmodified. What moved is two rendered literals of BASELINE.gzipBytes — the kind that must move when a baseline moves — and the comment records the direction without euphemism: "objectui#6683 down to 3177.7, objectui#6776 down to 3146.8, objectui#7122 UP to 3468.0 on the authorised raise". First upward move in that list, labelled as one.
BASELINE.commit: '34a1578ef' holds: the final commit touches three files (.mjs, its test, a changeset) and none reach the bundler, so the "differs only by this file" argument the constant depends on is still true.
What the raise is buying — stated so it is not misread later
⛔ Not a regression this branch introduced, and ⛔ not duplication. The duplication was already paid off by the family bump (−671 KB; 17.2.0-unique markers 92.3% → 1.0%). The residue is @objectstack/spec 17.3.0's own browser-dist growth — longer Zod .describe() doc strings shipping in the browser build. It is a cost this repo cannot pay from inside, recorded under the maintainer's explicit instruction 「抬上限,把 7685 弄绿」. Whether spec's browser build should carry authoring prose at all is the upstream question this hands back.
Also worth keeping visible: the earlier catalogue of "8 assertions / 6 files" was measured with --project unit, a subset of what CI shards. The true set was 24 assertions / 7 files, all four families now closed. A measurement narrower than the gate is not a reading of the gate.
⛔ Two things block landing, and neither is mine to decide
- The lookup-guard hunks are Designer PUTs half-filled
lookupdrafts that@objectstack/spec17.3.0 refuses (referenceis now required) — keep the draft client-side and do not PUT it (#7122 ruling, item 4 = A) #7714's by the 2026-09-05T23:00Z ruling (item 4/A1), and they are on this branch. They arrived through a PM rescue commit (9c1a1ac5f) when an agent died mid-task, not through a dispatch. They are in exactly one place today, so "must not be in both places" is not yet violated; they are gated and verified here; and removing them would destroy a verified fix, restore a known-broken save path, and put this PR back to red — the opposite of the instruction that produced it. Keeping them looks right on the merits, but closing Designer PUTs half-filledlookupdrafts that@objectstack/spec17.3.0 refuses (referenceis now required) — keep the draft client-side and do not PUT it (#7122 ruling, item 4 = A) #7714 as delivered-here allocates scope to another lane's card and needs its owner. Flagged on Designer PUTs half-filledlookupdrafts that@objectstack/spec17.3.0 refuses (referenceis now required) — keep the draft client-side and do not PUT it (#7122 ruling, item 4 = A) #7714. needs:contract-reviewis still owed, and correctly so. Section 10's old blanket claim was wrong as written: no repair here changes what any@object-ui/*package accepts or refuses, but the bump does —@object-ui/consolenow rejects anobject-master-detail-form.formTypevalue 17.2.0 accepted, andobject-grid.defaultSortleft the contract as an ADR-0087 D2 tombstone. That is an accept/reject change inherited from spec, and it is what the label is for.
Left draft, not enqueued, auto-merge unarmed. Green is not the same as landable, and the gap is these two decisions.
Two smaller items for whoever picks this up: objectui#6206 and objectui#6207 both look closable as resolved-upstream (17.3.0 moved to each one's own stated reason, and their exemptions are now deleted); and three per-chunk ceilings are passing but tight and were deliberately not raised — framework 68,550/71,000, ui-components 394,968/399,000, i18n-locales 449,305/455,000, of which this PR's ten capability labels cost +141 bytes.
Generated by Claude Code
Correction to my review above — two claims in it were wrong, and the numbering was staleThe tier contract review has run (maintainer-authorised, fable). It corrects my comment 5556211864 on three points, and I'd rather retract them here than let them stand as the record. 1. "Content that used to publish through 2. 3. I referred to "section 10". The body has sections 1–8; the clause-② section is §6. My numbering came from a superseded revision. What the review found that I missedBeing wrong in that direction did not make the PR cleaner — it made me miss four real clause-② items, and the strongest one has a defect:
Also: consumers on Verdict
The ownership question from ruling A1 (#7714) is unchanged and still not a review outcome. One upstream finding worth a card: Generated by Claude Code |
…han-spec refusal, and make its message accurate (objectui#7122)
The guard's predicate is `typeof reference === 'string' && reference.trim()
!== ''`, which is STRICTER than the contract. Measured on the installed
17.3.0, at field level and again through the whole document:
FieldSchema.safeParse({ type: 'lookup', label: 'L', reference: ' ' })
=> success = true
ObjectSchema.safeParse({ …, fields: { rel: { …, reference: ' ' } } })
=> success = true
— the spec ACCEPTS a whitespace-only target and both writers refuse it.
objectui being stricter than the platform is a divergence, not a neutral
choice, so it is now STATED: in both docblocks, in the changeset, in the
refusal message the author actually reads, and pinned in both writers'
tests so it cannot go silent again.
⭐ The `.trim()` is KEPT. A whitespace-only `reference` names no object, so
admitting it buys the author nothing and only moves the identical failure
past the guard, past the PUT and into a stored document, where it surfaces
with no field named. This guard exists precisely to stop a target-less
lookup being saved, and a blank target is a target-less lookup that happens
to parse. ⛔ No accept/refuse behaviour changed: the predicate is
byte-identical.
⚠️ The spec accepting `' '` while refusing `''` reads as an upstream gap
rather than deliberate latitude — its own refusal prose says "non-empty …
naming the target object", and `ObjectSchema.fields`' key grammar
(/^[a-z_][a-z0-9_]*$/) admits no whitespace-bearing name for it to resolve
to. Filed as objectstack#16126; the new pins are designed to go red when it
lands, which is the signal to retire the declaration, not to weaken the
guard.
The message is also corrected. It said the field "has none" for every
refused value, which is wrong for two of the four states: a non-string
`reference` is refused as `invalid_type` at that path, not as a missing
target (so "supply a target" is the wrong repair to prescribe for
`reference: 42`), and a whitespace-only one is not refused by the spec at
all, so the 422 the message promised could not be delivered for it. Each of
the four states now gets its own accurate diagnosis and consequence, via a
`describeUnusableTarget` split out in both writers.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0114Ytxr5sM1vdW19Y9WAx6E
…rth pin use it (objectui#7122)
Two pin-quality defects the contract review found in this branch's own
re-point, both of the shape this card has been fixing all round.
**The loose alternative discriminated nothing.** The regex was first written
`/\(ruled \d{4}-\d{2}-\d{2}\)|#\d{3,}/`, two alternatives meant to admit
either spelling upstream uses. But `#\d{3,}` matches any sentence citing
`objectui#NNNN`, and this repo's own hand-written messages routinely do — so
a locally authored sentence satisfied it, and telling those two apart is the
assertion's entire job. Only the `(ruled …)` half was ever load-bearing.
It was also unnecessary, which is the measurement that settled it. Both
spellings, read off the installed artifacts rather than recalled:
17.2.0 "… and this is an absolute URL (ruled 2026-08-11 on #7496)."
17.3.0 "… and this is an absolute URL (ruled 2026-08-11)."
The issue number never appears OUTSIDE that parenthesis, so `(ruled ` + a
date already matched both releases on its own. It is now
`/\(ruled \d{4}-\d{2}-\d{2}(?: on #\d{3,})?\)/`: the optional tail keeps the
17.2.0 spelling admissible — the two-form latitude the alternative was added
for — without admitting a bare local `#7122`. Verified against both measured
messages and against four local-prose counter-examples.
**The fourth file was the odd one out.**
`packages/plugin-form/src/submitRedirect.test.ts` had been re-pointed to the
literal `'ruled 2026-08-11'` while its three siblings used the shape — the
same brittleness one word along, a date instead of an issue number, and four
files disagreeing about what the pinned property even is. It now carries
`CITES_ITS_RULING` too, with the reasoning.
Test-only; no runtime byte moves.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0114Ytxr5sM1vdW19Y9WAx6E
…h the semantics (objectui#7122)
`AGENTS.md:239` grades objectui's own breaking changes `minor` — never
`major`, because any `major` in the fixed group pushes all 39 packages off
`@objectstack`'s major — with the breaking semantics written into the
changeset body. Three package entries were graded `patch` and should not
have been.
- `@object-ui/app-shell` + `@object-ui/plugin-designer` → `minor`. A
`lookup`/`master_detail` field with a missing or empty `reference` is now
refused before any PUT; a caller that previously got a request issued (and
accepted by a 17.2.0 backend) now gets a rejection and no request at all.
The changeset also declares the whitespace-only divergence and the message
correction (objectstack#16126).
- `@object-ui/types` → `minor`. The exported `ObjectSchemaClientExtensions`
narrows from `interface { editMode?: 'modal' | 'page' }` to
`type … = Record<never, never>`. Two breaking consequences, both named:
it no longer declares `editMode` (the spec's `ServiceObject` now carries
it, so `ObjectSchemaMetadata` still has it but code written against the
extension type alone does not), and `interface` → alias ends declaration
merging, so a consumer that reopened the interface to add its own member
no longer compiles.
The pin-repointing changeset additionally records the `CITES_ITS_RULING`
tightening and the fourth file (test-only, empty frontmatter, releases
nothing).
Gates: check-changeset-presence 0, -fixed 0, -no-major 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0114Ytxr5sM1vdW19Y9WAx6E
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Part of #7122
7be4b2456; §7's table on901c4b867; §3's arithmetic on34a1578ef. Each section names its own head — do not read one head across all of them. Base merged:origin/mainf5d2acc35. Everything the previous round left red is now green locally, including the two ceilings the maintainer authorised raising.⭐ This round applies the contract review's required remediation, which ruled
needs:contract-reviewnot dischargeable as it stands. The two items on this seat are done: the lookup guard's whitespace-only refusal is now a declared divergence rather than a silent one (§6 — and the message it prints is accurate for all four refused states), and the three package entries that graded objectui's own breaking behaviourpatchare raised tominorwith the breaking semantics written into the changeset bodies (AGENTS.md:239). §6 is replaced with the reviewer's own wording; two test-quality nits went with them. All of it is in §7a, measured on a freshly merged tree.⛔ Still draft.
readynot flipped, not enqueued, auto-merge not armed — review first.⭐ What this round changed
2761082b7(last CI read)901c4b867Type Check--continueTest(4 shards)Bundle Analysisvendor-objectstack2761082b7was 7 files / 24 assertions. Section 2 gives the reconciliation.1.
origin/mainwas merged first, and it barely moved the payloadfa73d58a9had already merged900f8d995; this round merged6bca0e4e8on top (7 commits, clean, no conflicts, no file overlap with anything repaired here).BASELINE.commit's "differs only by this file" claim true — but ⛔ it did not recover bytes. CI measured both sides: pre-mergee328e369c3468.0 KB, merged5ac59ff433467.8 KB.6bca0e4e8'sActionCallbackdeletion is worth 0.2 KB. That is the finding; it is not a saving.⛔
origin/mainwas deliberately not chased further. It moved again during the build (3f93fe47d); a tree that matches CI's merge ref exactly is impossible by construction, and the drift is what headroom is for.⭐ Merged again before the §7a push, because by then the branch was 38 commits behind:
f5d2acc35is merged in at7be4b2456, clean, no conflicts, 136 files, and — the reason it was not optional — the aggregate ceiling's 45.8 KB of headroom had been sized against ~4 commits of drift, not 38. ⛔ It did not cost bytes: see the merged-tree re-measure in §7a. No manifest dependency and no lockfile line moved in the merge (twoscriptsentries in the rootpackage.json, onetype-checkscript inpackages/vscode-extension), so no reinstall was needed.2. The real red set, and where each one went
24 assertions in 7 files, measured here before any repair and reproducing CI's aggregate exactly. All 24 are
@objectstack/spec17.3.0's, none is a regression this branch wrote.#7496tokensubmitRedirect.test.ts(console),WizardForm./ObjectForm.submitRedirect.test.tsxregistry-inputs-spec-parity.test.tsmasterDetailFormTypeManifest.test.ts,LocationField.optionalKeys.test.tsxCapabilityMultiSelectField.specParity-6285.test.tsxAlready red at the previous body's
f389bec90, or new since? All four families are 17.3.0 effects and were present on the branch before themainmerge — they were invisible to the old catalogue because that catalogue was written from a--project unitrun, which is a subset:pnpm test(what CI shards) also runs the per-package projects and thedomprojects, where every one of these 24 lives. ⭐ Nothing appeared because of the merge, and nothing disappeared without being fixed.A — 14 assertions, one fix, and why it qualified as a family
Every one was
expected '(message)' to contain '#7496', across five distinct refusal messages, while the siblingtoContain('RELATIVE path only')assertions stayed green. 17.3.0 kept every refusal, its reasoning and its prescription, and restated provenance as(ruled 2026-08-11).So the pins asserted the citation form, not the citation — the objectui#7702 defect shape, which
mainhas since corrected in its own lane. They now assert the durable property through a documentedCITES_ITS_RULING, which does the job the#7496assertion existed for: discriminating the spec's own prose from a locally hand-written sentence (the load-bearing mutation probe inObjectForm.submitRedirect.test.tsx's header depends on exactly that)./\(ruled \d{4}-\d{2}-\d{2}\)|#\d{3,}/, and the loose alternative discriminated nothing — this repo's own hand-written messages routinely citeobjectui#NNNN, so a local sentence satisfied it, which is exactly the case the assertion exists to reject. It was also unnecessary: measured on both installed artifacts, 17.2.0 prints(ruled 2026-08-11 on #7496)and 17.3.0 prints(ruled 2026-08-11), so the issue number never appears outside that parenthesis and(ruled+ a date already matched both. It is now/\(ruled \d{4}-\d{2}-\d{2}(?: on #\d{3,})?\)/— both upstream spellings still match, a bare local#7122no longer does.⛔ Not re-pinned to the new prose verbatim, which would move the brittleness one release along instead of removing it.
B — three exemptions the contract outgrew
Four of the five reds were one fact wearing four assertion names, two of which appear to contradict each other ("a published key must lose its entry" vs "spec no longer declares
object-grid.defaultSort"). Resolved against the installed artifact, not the assertion names: neither reading is right. 17.3.0 madeobject-grid.defaultSortan ADR-0087 D2 tombstone — member isz.never().optional(), description opens[REMOVED] … removed in @objectstack/spec 17 (ADR-0049), andsafeParseanswersinvalid_typeexpected: 'never'at that path. It left the authorable set while staying listed, which is precisely how the eight tombstones before it were harvested. Its exemption, its GA-pending entry and its carve-out row are deleted.The block's prose claiming a tombstone "would NOT make them stale here" is corrected — its own cited counter-example, the record_picker trio, was harvested exactly that way.
The fifth emptied
OFF_SPEC_ARM_EXEMPTIONSentirely. Both arms converged upstream in the direction their own reasons named, measured with the gate's own probes:element:number.filteraccepts[]and judges content atfilter.0— objectui#6206's first option, "widening the spec entry to the ViewFilterRule array form every sibling filter uses";object-grid.dataanswersinvalid_unionatdata.provider, i.e. the discriminatedViewDataSchemashapeObjectGridSchema.dataalready resolved to — objectui#6207's "convergence is upstream".⛔ Neither was closed by editing a declaration; both declarations are byte-identical. objectui#6206 and objectui#6207 are closable as resolved-upstream.
C — two negative pins, re-derived rather than inverted
object-master-detail-form.formTypepinned that the manifest enum does not make a value unpublishable. 17.3.0 closed that gap deliberately —invalid_valuenaming the honoured pair, plus a bespoke prescription for the near-misswizardciting ADR-0001 and the renderer measurement behind it. Graded as an intended tightening, so the pin now records that both authorities agree, checks each independently, keeps a positive control onsimple/tabbed, and pins the refusal's shape so "the enum closed" cannot be confused with a dropped key or a cross-field rule.LocationField.optionalKeyspinned that the location value schema is non-strict. 17.3.0 closed the shape (unrecognized_keys, naming the retiredlatitude/longitudepair and prescribing the rename). The test carried a note for this exact event — "if the spec ever turns strict, the guard could then be delegated to it" — and the re-derived answer is no: the fence guards the object handed toonChange, and no parse stands on that path, so strictness changed the consequence of a spread regression (a loud refusal at the next parsing boundary instead of a silent survival), not the widget's exposure to it. The key-level assertions stay and now say why; the strictness itself is pinned.D — the ninth platform capability
17.3.0 declares
manage_org_presentationand the curated set carried eight, so the picker fell back to the registry's English label in every locale — the exact defect objectui#6285 filed whenmanage_sharingdid this. objectui#6285 already ruled the curated set is the spec's set in both directions, so carrying the name executes that ruling.The English label is the spec artifact's own (⚠️ The nine non-English strings are composed from each pack's own established sibling vocabulary (
Manage Organization Presentation), read off the installed build. It is authored everywhere the widget's docblock requires:useFieldTranslation.tsand all ten locale packs.manage_org_users,manage_platform_settings) rather than machine-translated — a native review pass is welcome on them; nothing behavioural depends on the wording.check:i18n-keys(now 114 exactly-checked members, was 113),check:i18n-driftandcheck:i18n-dead-keysall exit 0.3. ⭐ The ceiling raise — authorised, and what the bytes buy
Raising a gate ceiling is a gate weakening and sits on the manual floor, so it is a human's decision. The authorisation, verbatim:
recorded on #7122 as decision batch 1 item 1 = B + A — a one-time, cause-recorded adjustment of exactly the measured delta, with the upstream card filed alongside.
⛔ This is not routine growth and must not be read as one. The residue is
@objectstack/spec17.3.0's own browser-dist growth: +292.2 KB gzip on the spec package alone, whose measured mechanism is that 17.3.0 lengthened the Zod.describe()doc strings shipped in the browser build. It is authoring documentation prose, delivered on every page load.⛔ It is not duplication. That was the larger, separate problem and this branch already fixed it by moving the
@objectstack/*family together in the lockfile: −671 KB, with markers unique to 17.2.0 falling from 92.3% to 1.0% of 104 and the single survivor accounted for. No chunk entered or left the closure; the eager chunk count is unchanged.⇒ ⭐ The honest long-term fix is upstream, and this ceiling is its marker, not its answer. A
describe()-stripped browser build returns ~292 KB to every consumer of the spec, not just this console. Filed as objectstack#16063, and recorded in the gate as the restore condition: when it lands, re-measure and bring ceiling and baseline back down together.The arithmetic, so the sensitivity constraint is checkable by reading
Four constants, two ceiling/baseline pairs. Measured by
pnpm build(exit 0, 43/43) readingapps/console/dist/eager-closure.jsonon34a1578ef— ⛔ not taken from CI's report, not from an earlier round, not extrapolated.MAX_EAGER_CLOSURE_GZIP_BYTESBASELINE3d257c85a34a1578efPER_CHUNK_GZIP_CEILINGS['vendor-objectstack']PER_CHUNK_BASELINE['vendor-objectstack']vendor-objectstackBoth are strictly below the 91,136-byte regression the gate exists to catch, so it still reds on a repeat of objectui#5266 — the ceiling is not decorative. Both match the proportion the file already used rather than a newly invented one. Headroom also absorbs the unclosable merge-ref gap: CI's merged-head reading is 3,551,027 bytes against this tree's 3,551,191, a 164-byte difference.
BASELINE.commitnames34a1578ef.7be4b2456merged 38 commits oforigin/mainplus three authored commits on top of it. The baseline is nonetheless still sound, and by measurement rather than by argument — §7a re-rancheck:eager-closureon the merged tree and the aggregate came in at 3,551,183, eight bytes below the pinned 3,551,191. Read §7a's re-measure as the live figure; this section is the derivation that produced the constants.⛔ No other ceiling was moved. The three per-chunk lines that still pass were left exactly as they are, and they are reported here because two are tight:
i18n-localesui-componentsframeworki18n-locales, which fits — so this is not a third ceiling/baseline pair and is deliberately not presented as one. ⛔ No exemption added, no import made lazy.check:eager-closureexits 0 with Ceiling sensitivity ✅ on all five ceilings. Ceiling freshness reports "not applicable to a local run" (it compares a merge ref against the branch it would land on); CI evaluates it.4. The two rescue commits — what was verified, and what was changed
9c1a1ac5fand2761082b7were PM rescue commits carrying work two killed agents never gated, both marked INCOMPLETE AND UNREVIEWED. Nothing in them was assumed correct. Every load-bearing claim was re-measured against the installed 17.3.0 artifact:MetadataService.ts+MetadataFieldsPage.tsx— the lookup guardFieldSchema's 49 declared types, exactlylookupandmaster_detailare refused at pathreferenceon a minimal document, and no other type is refused at all. Empty-stringreferenceis refused too, which is what the guard's.trim()matches.2761082b7had been mid-repair of a block the first agent inserted inside another function's docblock; the relocation is correct and the file now type-checksuser:profilethree-site removal (placeholders.tsx,known-schema-types.ts,block-types.ts)PageComponentTypemeasured at 32 options withuser:profileandelement:formboth absent — exactly the rescue commit's claim, and exactly ruling B1ObjectSchemaClientExtensionsretirement (field-types.ts)ObjectSchemameasured at 43 keys witheditModepresent, so the local member was a genuine fork; retiring it executes the pin's own docblock prescription (ruled item 2)plugin-detailsectionsinput descriptionexport-options-spec-parity.test.ts(2761082b7)mainand merged cleanly; this is one assertion inside it re-pointed at the prescriptive half. Both new substrings confirmed present in 17.3.0's messageChanged rather than kept: nothing in the rescue commits was reverted. The corrections this round made are in files the rescue commits did not touch (sections 2A–2D), plus the budget file.
5.⚠️ Ownership flag — the lookup guard is #7714's work, and it is here
#7122's ruling of 2026-09-05T23:00Z (item 4, A1) says the designer hunks "belong to #7714's PR — the driver either moves them there or drops them from this branch; ⛔ not both places."
They are currently in one place: this branch. #7714 is
pm:queue, unassigned, with no PR, so "moving them there" would mean opening a PR in another card's lane, which is outside this dispatch's fence. They were kept rather than dropped because dropping them destroys a verified product fix and leaves a known-broken save path, and re-adding is cheap.⇒ This needs a decision before landing, and it is not this seat's to make: either #7714 closes as delivered-here (and its dogfood reproduction becomes its own follow-up), or its owner takes these hunks and they come off this branch. ⛔ They must not land twice. The work is preserved in this branch's history at
9c1a1ac5feither way.6. Clause ② —
yes, on three authored repairs and on the inherited setAuthored by this branch (objectui's own accept/refuse or surface changes):
@object-ui/app-shellMetadataService.saveFields/saveObjectand@object-ui/plugin-designerMetadataFieldsPagenow refuse alookup/master_detailfield whosereferenceis missing or empty, before any PUT (previously PUT, and accepted by a 17.2.0 backend). Objectui-authored, ruled When bumping @objectstack/spec past objectstack#14075: list-view spec-parity pins need the CalendarConfig titleField-optional update #7122 item 4 = A; measured against the installed 17.3.0: exactly those two ofFieldSchema's 49 types are refused atreference(custom), and the wholeObjectSchemadocument fails with them. Ownership per ruling A1 is open (§5).reference, which the spec accepts — declared deliberate, on the ground that a whitespace-onlyreferencenames no object at all, so admitting it would only move the identical failure past the PUT into a stored document. Declared in both writers' docblocks, in the changeset and in the refusal message itself, pinned in both writers' tests, and the spec's acceptance of' 'filed upstream as objectstack#16126.@object-ui/componentsregisterPlaceholders()no longer registersuser:profile; a page naming it renders "Unknown component type" in opted-in hosts.@object-ui/cliobjectui checknow warns on that type (exit code unchanged). Ruled B1; upstream objectstack#14159.@object-ui/coreSPEC_ACTION_KEYSgainsoperation/patch, soclassifyActionKeysand the dev-mode unknown-key warning stop reporting them (ruled item 3; recorded in the inventory because the forward-parity gate's owed set has no runtime reader for either key).@object-ui/typesObjectSchemaClientExtensionsis retired toRecord<never, never>;ObjectSchemaMetadata.editModeis now carried by the spec (ruled item 2).Inherited from
@objectstack/spec17.3.0, reaching this repo's console build and test corpus through the lockfile (consumers on^17.xalready resolve 17.3.0 — npmlatestsince 2026-09-04):formTypenarrows tosimple | tabbed(invalid_value);object-grid.defaultSortis an ADR-0087 tombstone (invalid_typeexpectingnever);FieldSchema.referencerequired on the two relationship types;PageComponentSchemarefusesuser:profileby name — which the Studio save gate (validateMetadataDraft→PageSchema) now enforces;element:number.filterrecord form andobject-grid.dataarray form refused; location values strict. ⛔ None of these is a product-path change in objectui except the Studiouser:profilerefusal: the Studio gate does not validate blockproperties, no runtime path parsesComponentPropsMap, and objectui's manifest gate already refusedformType: 'wizzard'(objectui#5939).Contract-neutral: the
#7496re-pin, the two re-derived negative pins, the exemption harvests, themanage_org_presentationlabel (selectability is the server registry's; only the label localises), the flow-inspector renames (not on any published entry), theValidationPanellabel, thedata-objectstacktype alias, the ceiling raise (§3).needs:contract-reviewstays on both carriers until the tier review passes.7. Gates
Exit codes captured before any pipe (
cmd (redirect) file 2(redirect)1; EXIT=$?), and every verdict read from the gate's own printed line rather than a bare$?.pnpm buildturbo run type-check --continue--continue, so this is a complete blocker list, not a list truncated at the first failurepnpm test— 4 shards, the CI invocationcheck:eager-closurescripts/__tests__/check-eager-closure-budget.test.tscheck:i18n-keys·check:i18n-drift·check:i18n-dead-keyscheck:control-bytes·type-check:scriptscheck-changeset-presenceeslint --no-inline-configon all 21 changed source/test filesno-explicit-anywarnings)pnpm test, not--project unit. The previous body's suite numbers came from the latter, which is a subset — that is why its red set was smaller than CI's.7a. The contract-review remediation, and its gates on the merged tree
7be4b2456What changed, file by file. 8 source/test files and 3 changesets.
packages/app-shell/src/services/MetadataService.ts.trim()divergence declared in the docblock (measured both ways, upstream card named); the refusal message split into four accurate diagnoses viadescribeUnusableTargetpackages/plugin-designer/src/MetadataFieldsPage.tsx…/MetadataService.specKeyReference.test.ts·…/MetadataFieldsPage.specKeyReference.test.tsx' ', this writer refuses it, and the refusal says so on screen.apps/console/…/submitRedirect.test.ts·plugin-form/…/{Wizard,Object}Form.submitRedirect.test.tsxCITES_ITS_RULINGtightened (§2A)packages/plugin-form/src/submitRedirect.test.ts'ruled 2026-08-11'while its three siblings pinned the shape, i.e. the same brittleness one word along; now usesCITES_ITS_RULINGtoo.changeset/7122-lookup-target-required-before-save.md@object-ui/app-shell+@object-ui/plugin-designerpatch→minor, breaking semantics stated, divergence declared.changeset/7122-spec-17-3-0-parity-reconciliation.md@object-ui/typespatch→minor, with both breaking consequences named:editModeleaves the exported extension type, andinterface→ alias ends declaration merging.changeset/7122-spec-17-3-0-pin-repointing.mdGates. Exit codes captured before any pipe, all on the merged tree.
pnpm buildturbo run type-check --continuepnpm test— 4 shards, the CI invocationTestshards on7be4b2456are the sole merged-tree test evidencecheck:eager-closurenode scripts/check-changeset-presence.mjsnode scripts/check-changeset-fixed.mjs·check-changeset-no-major.mjsminor, whichAGENTS.md:239prescribes; amajorwould push all 39 fixed-group packages off@objectstack's majorcheck:control-bytescheck:designer-field-key-parity·check:spec-symbols·check:unreferenced-sources·check:handler-key-reads·check:upstream-port-parityeslint --no-inline-configthrough each package's own lint binary, all 8 changed filesno-explicit-anywarnings, none on a touched line)⭐ The merged-tree closure re-measure — the margin, on the record
38 commits of
maincost the eager closure nothing, and returned 613 bytes onframework. ⛔ No constant was edited; every ceiling below is the one this PR already carries.34a1578ef7be4b2456)vendor-objectstacki18n-localesui-componentsframeworkThe three lines the review flagged as passing but tight are all still passing: two are byte-identical across the merge and
frameworkgained 613 bytes of room. The aggregate came in 8 bytes below the pinnedBASELINE.gzipBytes, so the raise the maintainer authorised still covers the payload with the same 0.50x proportion it was sized at, and no second raise is needed or taken.8. Still open, with owners
@object-ui/typesspec floor^17.0.0vs^17.3.0describe()-stripped browser build@objectstack/specaccepts a whitespace-onlyreferencewhere it refuses'', so objectui is compensating for it (§6, §7a)minorobject-grid.defaultSort/ arm-exemption convergencerecord:detailssection-entry designer controlsGenerated by Claude Code